package project2;

import java.io.IOException;
import java.sql.Timestamp;
import java.util.Arrays;

import misc.not_Legal_Exception;
import evolution_superclasses.Evolutionary_Loop;
import evolution_superclasses.Genotype;
import evolution_superclasses.Phenotype;

public class spiking_neuron_loop extends Evolutionary_Loop {
	private spiking_neuron_Population _snpop;
	private spiking_neuron_Fitness_Evaluation _snfe;
	private spiking_neuron_Genetic_Operators _sngo;

	private int _highestFrom = 0;
	private double _allTimeHighest = 0;
	private Genotype _allTimeHighestIndividual;
	private String _plotPath = "";

	public spiking_neuron_loop() {
		super();
		set_plotList(new boolean[]{true,true,false,true});
	}
	
	public static void main(String[] args) {
		spiking_neuron_loop snl = new spiking_neuron_loop();
		boolean die = false;
		try {
			snl.begin();
//			snl.TEST2();
		} catch (Exception e) {
			die = true;
			e.printStackTrace();
		}
		if(!die){
			try {
				System.out.println("Again? (1/0)");
				if(snl._ih.readInt()==1){
					main(null);
					return;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	//Just a test to see if spike train generation works
	public void TEST() throws IOException, not_Legal_Exception{
		spiking_neuron_genotype g = new spiking_neuron_genotype(5);
		g.edit_params(0, 100);
		g.edit_params(1, 200);
		g.edit_params(2, -55000);
		g.edit_params(3, 2000);
		g.edit_params(4, 40);
		spiking_neuron_phenotype p =  (spiking_neuron_phenotype) g.generatePhenotype();
		double[] train = p.get_spikeTrain();
		_plotter.plotSpikeTrain("Spikey", "Proj2/test", train);
		
		spiking_neuron_Fitness_Evaluation snfe = new spiking_neuron_Fitness_Evaluation(1,4);
		snfe.setTarget(4);
		System.out.println("STDistance: "+snfe.spikeTimeDistanceMetric(p));
		System.out.println("STInterval: "+snfe.spikeTimeIntervalMetric(p));
		System.out.println("WaveForm: "+snfe.WaveFormDistanceMetric(p));
	}
	
	//In order to create table for report. each N amounts to about 75 seconds
	public void TEST2() throws Exception{
		_plot = false;
		initializePrint(false);
		_print = false;
		initializeEndConditions(200);
		initializeGenotypeSize(5);
		initializePopulationSize(500);
		initializeGeneticOperatiors(0.2, 0.5);
		initializeValueMutationRate(0.01);
		initializeEliteism(1);
		initializeAdultPoolSize(0);
		initializeSelectionProtocol(0);
		initializeSelectionMechanism(2);
		
		double best, sum;
		int N = 100, sdm, target, i;
		long start,end;
		for(sdm = 0; sdm < 2; sdm++){
			for(target = 1; target <= 4; target++){
				best = 0;
				sum = 0;
				start = System.nanoTime();
				initializeSpikeFitnessEvaluation(target,sdm);
				for(i = 0; i < N; i++){
					_allTimeHighest = 0;
					clearArrayLists();
					initializeEndConditions(200);
					initializePopulation();
					
					evolutionLoop();
					sum += _allTimeHighest;
					if(_allTimeHighest > best) best = _allTimeHighest;
				}
				sum /= N;
				end = System.nanoTime();
				System.out.println("SDM: "+sdm+" Target: "+target+"\nAfter "+N+" trials, "+sum+" was the average, and "+best+
						" the best fitness values\n Took "+((double)(end-start)/1000000000)+ " seconds\n");
			}
		}
		System.out.println("finished");
	}
	
	//Set params to -1 if one would like to initialize the values
	@Override
	public void begin() throws Exception{
		clearArrayLists();

		int target = 1;
		set_title("");
		_plotPath = "/Proj2/train_"+target+"/";
		set_filename(_plotPath+"chart");
		_plot = true;
		
		initializePrint(false);
		initializeEndConditions(200);
		initializePopulationSize(500);
		initializeGenotypeSize(5);
		initializeGeneticOperatiors(0.2, 0.5);
		initializeValueMutationRate(0.01);
		initializeEliteism(1);
		initializeSpikeFitnessEvaluation(target,0);
		initializeAdultPoolSize(0);
		initializeSelectionProtocol(0);
		initializePopulation();
		initializeSelectionMechanism(2);
		
		evolutionLoop();
		if(!_print)//Print them anyway
			printEndResults();
	}

	protected void initializeValueMutationRate(double m) {
		if(m>-1){
			_sngo.set_valueMutationRate(m);
			return;
		}
		try {
			System.out.println("At what rate should genotype param mutate? (type double, between 0 and 1)");
			double tmp = _ih.readDouble();
			if(tmp < 0 || tmp > 1)
				throw new IllegalArgumentException();
			_sngo.set_valueMutationRate(tmp);
		} catch (Exception e) {
			System.out.println("Illegal value detected, try again");
			initializeValueMutationRate(m);
			return;
		}
	}

	@Override
	protected void createNewPopulation() {
		double best = get_bestFitness();
				
		if(_plot)
			_plotter.plotSpikeTrain("gen "+(get_generationNumber()-1), _plotPath+(get_generationNumber()-1), ((spiking_neuron_phenotype)_bestInduvidual.generatePhenotype()).get_spikeTrain());
		
		if(best>_allTimeHighest){
			_allTimeHighest = best;
			_highestFrom = get_generationNumber()-1;
			spiking_neuron_genotype bg1 = new spiking_neuron_genotype(_genSize);
			bg1.set_params(((spiking_neuron_genotype)_bestInduvidual).get_params().clone());
			_allTimeHighestIndividual = bg1;
		}
		
		int nr = 0, i = 0;
		Genotype[] tmp = new Genotype[_popSize];		

		while(i < _freepass){
			spiking_neuron_genotype bg1 = new spiking_neuron_genotype(_genSize);

			bg1.set_params(((spiking_neuron_genotype)_bsp.get_adultPool()[i].get_genotype()).get_params().clone());
			tmp[i++] = bg1;
		}
		
		while(nr < _popSize-_freepass){
			spiking_neuron_genotype bg1 = new spiking_neuron_genotype(_genSize);
			spiking_neuron_genotype bg2 = new spiking_neuron_genotype(_genSize);

			bg1.set_params(((spiking_neuron_genotype)_bsm.get_parents()[nr++].get_genotype()).get_params().clone());
			bg2.set_params(((spiking_neuron_genotype)_bsm.get_parents()[((nr++)%_bsm.get_parents().length)].get_genotype()).get_params().clone());

			if( _crossOverRate > _rand.nextDouble())
				_sngo.crossOver(bg1, bg2);

			if( _mutationRate > _rand.nextDouble())
				_sngo.mutation(bg1);
			if( _mutationRate > _rand.nextDouble())
				_sngo.mutation(bg2);
			
			tmp[i++] = bg1;
			if(i<_popSize) //In case it's an odd number.
			tmp[i++] = bg2;	
		}
		_snpop.set_genotypes(tmp);
	}

	@Override
	protected void initializeAndDevelopGeneration() throws not_Legal_Exception {
		int index = 0;
    	double variance = 0,standardDev, avg,total = 0;
		_generation = new spiking_neuron_phenotype[_popSize];
		
		for(Genotype g : _snpop.get_genotypes()){
			Phenotype p = g.generatePhenotype();
			_generation[index++] = p;
			p.set_fitness(_snfe.evaluateFitness(p));
			total += p.get_fitness();
		}
		
		avg = total/_popSize;
		_bsm.set_sigma_estimated(avg);

		for(Phenotype p : _generation){
    		variance += Math.pow(p.get_fitness()-avg,2);
    	}
    	standardDev = Math.sqrt(variance/_popSize);

    	Arrays.sort(_generation);
		set_bestFitness(_generation[0].get_fitness());
		set_bestInduvidual(_generation[0].get_genotype());
		set_totalFitness(total);
		set_worstFitness(_generation[_popSize-1].get_fitness());

		_max.add(get_bestFitness());
		_avg.add(avg);
		_min.add(get_worstFitness());
		_stdDerivance.add(standardDev);
	}
	
	protected void initializeSpikeFitnessEvaluation(int i, int nr)
		throws not_Legal_Exception, Exception {
		if(i>-1){
			_snfe = new spiking_neuron_Fitness_Evaluation(i, nr);
			return;
		}
		System.out.println("Which target spike train would you try to achive? (int between 1 and 4)");
		try {
			int tmp = _ih.readInt();
			System.out.println("Now, what kind of fitness evaluation will you choose? \n(0 is spikeTimeDistanceMetric\n1 is spikeTimeIntervalMetric\n2 is WaveFormDistanceMetric)");
			_snfe = new spiking_neuron_Fitness_Evaluation(tmp,_ih.readInt());
		} catch (Exception e) {
			System.out.println("Illegal value detected, try again");
			initializeFitnessEvaluation(i);
		}
	
	}

	@Override
	protected void initializeFitnessEvaluation(int i)
			throws not_Legal_Exception, Exception {
		initializeSpikeFitnessEvaluation(-1,-1);

	}

	@Override
	protected void initializeGeneticOperatiors(double m, double c) {
		if(m>-1){
			_mutationRate = m;
			_crossOverRate = c;
			_sngo = new spiking_neuron_Genetic_Operators(_genSize);
			return;
		}
		try {
			System.out.println("At what rate should genotypes mutate? (type double, between 0 and 1)");
			_mutationRate = _ih.readDouble();
			System.out.println("At what rate should genotypes suffer crossOvers? (type double, between 0 and 1)");
			_crossOverRate = _ih.readDouble();
			if(_mutationRate < 0 || _mutationRate > 1 || _crossOverRate < 0 || _crossOverRate > 1)
				throw new IllegalArgumentException();
		} catch (Exception e) {
			System.out.println("Illegal value detected, try again");
			initializeGeneticOperatiors(m,c);
			return;
		}
		_sngo = new spiking_neuron_Genetic_Operators(_genSize);
	}

	@Override
	protected void initializePopulation() {
		_snpop = new spiking_neuron_Population(_genSize, _popSize);
	}
	
	@Override
	protected void printEndResults() {
		System.out.println("\nEnd fitness: "+_endBestFitness+" after "+(get_generationNumber()-1)+" generations");
		if(_print){
			System.out.println("The best individual at the end was this this:");
			System.out.println(_bestInduvidual+" Wich translate into\n"+_bestInduvidual.get_phenotype());
			System.out.println("\nThe all time best individual was this from "+_highestFrom+", with fitness of "+_allTimeHighest);
			System.out.println(_allTimeHighestIndividual+" Wich translate into\n"+_allTimeHighestIndividual.generatePhenotype());
		}
	}

}
